﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.IO;
using Framework.DataObjects;
using Framework.Interface;

namespace Framework
{
    /// <summary>
    /// Simpler API then StringBuilder for building formatted documents including source code, can be used with any
    /// type of TextWriter.
    /// <example>
    /// StringBuilder result;
    /// using (TextBuilder buffer = TextBuilder.Instance(out result))
    /// {
    ///    buffer.Indent++;
    ///    buffer.WriteLine("Indent David"); // System.CodeDom.Compiler.IntentingTextWriter fails to write indent on 1st call.
    ///    buffer.Indent--;
    /// 
    ///    buffer.Write("David");
    ///    buffer.Write(" was ");
    ///    buffer.Write("here");
    ///    buffer.WriteLine();
    /// 
    ///    buffer.WriteMultiValueLine("David was here");
    /// 
    ///    buffer.WriteNameValueLine("2 o'clock", "Do Stuff");
    ///    buffer.WriteNameValueLine("3 o'clock", "Do More Stuff");
    ///    buffer.WriteNameValueLine("4 o'clock", "Rest");
    ///    buffer.WriteNameValueLine("6 o'clock", "Do Something Else");
    /// 
    ///    buffer.WriteLine("Apple");
    ///    buffer.WriteLine("Grape");
    ///    buffer.WriteLine("Orange");
    /// 
    ///    buffer.NameValueSeperator = "|";
    ///    buffer.NameValuePadding = 0;
    /// 
    ///    buffer.WriteNameValue("Apple", "Round");
    ///    buffer.WriteNameValue(",Orange", "Round");
    ///    buffer.WriteNameValue(",Bannana", "Crescent");
    ///    buffer.WriteLine();
    /// 
    ///    buffer.NewLine = "<br />";
    /// 
    ///    buffer.WriteLine("<h1>Title</h1>");
    ///    buffer.WriteLine("<p>Hello World</p>");
    ///    buffer.WriteLine("<p>Hi Everybody</p>");
    /// 
    ///    buffer.NewLine = "\r\n";
    /// 
    ///    buffer.WriteLine();
    /// 
    ///    buffer.WriteLine();
    ///    buffer.WriteLine("Heading");
    /// 
    ///    buffer.WriteLine("Level 0 - a");
    ///    buffer.WriteLine("Level 0 - b");
    /// 
    ///    buffer.Indent++;
    ///    buffer.WriteLine("Level 1 - a");
    ///    buffer.WriteLine("Level 1 - b");
    /// 
    ///    buffer.Indent++;
    ///    buffer.WriteLine("Level 2 - a");
    ///    buffer.WriteLine("Level 2 - b");
    /// 
    ///    buffer.Indent--;
    ///    buffer.WriteLine("Level 1 - c");
    ///    buffer.WriteLine("Level 1 - d");
    /// 
    ///    buffer.Write("Level 1");
    ///    buffer.Write(" - e");
    ///    buffer.WriteLine();
    /// 
    ///    buffer.Indent--;
    ///    buffer.WriteLine("Level 0 - c");
    ///    buffer.WriteLine("Level 0 - d");
    /// 
    ///             }
    ///             _.P(result.ToString());
    /// </example>
    /// </summary>
    public class TextBuilder : TextWriter
    {
        #region Instance
        public static TextBuilder Instance(out StringBuilder output)
        {
            output = new StringBuilder();

            return new TextBuilder(new StringWriter(output));
        }

        public static TextBuilder Instance(out StringBuilder output, string endOfLine)
        {
            output = new StringBuilder();

            return new TextBuilder(new StringWriter(output), endOfLine);
        }

        public static TextBuilder Instance(out StringBuilder output, string endOfLine, string tab, string nameValueSeperator, int nameValuePadding)
        {
            output = new StringBuilder();

            return new TextBuilder(new StringWriter(output), endOfLine, tab, nameValueSeperator, nameValuePadding, 0);
        }
        #endregion

        #region C'tor
        protected TextBuilder()
        {
        }

        public TextBuilder(TextWriter writer)
            : this(writer, "\r\n")
        {
        }
        public TextBuilder(TextWriter writer, int indent)
            : this(writer, "\r\n", "\t", ": ", 30, indent)
        {
        }
        public TextBuilder(TextWriter writer, string endOfLine)
            : this(writer, endOfLine, "\t", ": ", 30, 0)
        {
        }
        public TextBuilder(TextWriter writer, string endOfLine, string tab, string nameValueSeperator, int nameValuePadding, int indent)
        {
            Initialize(writer, endOfLine, tab, nameValueSeperator, nameValuePadding, indent);
        }
        #endregion

        #region Initialize
        protected void Initialize(TextWriter writer, string endOfLine, string tab, string nameValueSeperator, int nameValuePadding, int indent)
        {
            Writer = writer;
            Indent = indent;
            TabsPending = true;
            TabString = tab;
            NewLine = endOfLine;
            NameValueSeperator = nameValueSeperator;
            NameValuePadding = nameValuePadding;
            ReplacePropertyDelimiter = ReplacePropertyDelimiterType.Brace;
            Data = new Dictionary<string, object>(FrameworkConstant.DEFAULT_COMPARER_IGNORECASE);
        }
        #endregion

        #region Close
        public override void Close()
        {
            Writer.Close();
        }
        #endregion

        #region Flush
        /// <summary>Flushes the stream.</summary>
        public override void Flush()
        {
            Writer.Flush();
        }
        #endregion

        #region OutputTabs
        protected virtual void OutputTabs()
        {
            if (TabsPending)
            {
                for (var num1 = 0; num1 < Indent; num1++)
                {
                    Writer.Write(TabString);
                }
                TabsPending = false;
            }
        }
        #endregion

        #region Write
        public override void Write(bool value)
        {
            OutputTabs();
            Writer.Write(value);
        }

        /// <summary>Writes a character to the text stream.</summary>
        /// <param name="value">The character to write. </param>
        public override void Write(char value)
        {
            OutputTabs();
            Writer.Write(value);
        }

        /// <summary>Writes a character array to the text stream.</summary>
        /// <param name="buffer">The character array to write. </param>
        public override void Write(char[] buffer)
        {
            OutputTabs();
            Writer.Write(buffer);
        }

        /// <summary>Writes the text representation of a Double to the text stream.</summary>
        /// <param name="value">The double to write. </param>
        public override void Write(double value)
        {
            OutputTabs();
            Writer.Write(value);
        }

        /// <summary>Writes the text representation of an integer to the text stream.</summary>
        /// <param name="value">The integer to write. </param>
        public override void Write(int value)
        {
            OutputTabs();
            Writer.Write(value);
        }

        /// <summary>Writes the text representation of an 8-byte integer to the text stream.</summary>
        /// <param name="value">The 8-byte integer to write. </param>
        public override void Write(long value)
        {
            OutputTabs();
            Writer.Write(value);
        }

        /// <summary>Writes the text representation of an object to the text stream.</summary>
        /// <param name="value">The object to write. </param>
        public override void Write(object value)
        {
            OutputTabs();
            Writer.Write(value);
        }

        /// <summary>Writes the text representation of a Single to the text stream.</summary>
        /// <param name="value">The single to write. </param>
        public override void Write(float value)
        {
            OutputTabs();
            Writer.Write(value);
        }

        /// <summary>Writes the text representation of a Single to the text stream.</summary>
        /// <param name="value">The single to write. </param>
        public override void Write(decimal value)
        {
            OutputTabs();
            Writer.Write(value);
        }

        /// <summary>Writes the specified string to the text stream.</summary>
        /// <param name="s">The string to write. </param>
        public override void Write(string s)
        {
            OutputTabs();
            Writer.Write(s);
        }

        /// <summary>Writes out a formatted string, using the same semantics as specified.</summary>
        /// <param name="arg0">The object to write into the formatted string. </param>
        /// <param name="format">The formatting string. </param>
        public override void Write(string format, object arg0)
        {
            OutputTabs();
            Writer.Write(format, arg0);
        }

        /// <summary>Writes out a formatted string, using the same semantics as specified.</summary>
        /// <param name="arg">The argument array to output. </param>
        /// <param name="format">The formatting string to use. </param>
        public override void Write(string format, params object[] arg)
        {
            OutputTabs();
            Writer.Write(format, arg);
        }

        /// <summary>Writes out a formatted string, using the same semantics as specified.</summary>
        /// <param name="arg0">The first object to write into the formatted string. </param>
        /// <param name="arg1">The second object to write into the formatted string. </param>
        /// <param name="format">The formatting string to use. </param>
        public override void Write(string format, object arg0, object arg1)
        {
            OutputTabs();
            Writer.Write(format, arg0, arg1);
        }

        /// <summary>Writes a subarray of characters to the text stream.</summary>
        /// <param name="count">The number of characters to write. </param>
        /// <param name="buffer">The character array to write data from. </param>
        /// <param name="index">Starting index in the buffer. </param>
        public override void Write(char[] buffer, int index, int count)
        {
            OutputTabs();
            Writer.Write(buffer, index, count);
        }
        #endregion

        #region WriteLine
        /// <summary>Writes a line terminator.</summary>
        public override void WriteLine()
        {
            OutputTabs();
            Writer.WriteLine();
            TabsPending = true;
        }

        /// <summary>Writes the text representation of a Boolean, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">The Boolean to write. </param>
        public override void WriteLine(bool value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes a character, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">The character to write. </param>
        public override void WriteLine(char value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes the text representation of an object, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">The object to write. </param>
        public override void WriteLine(object value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes a character array, followed by a line terminator, to the text stream.</summary>
        /// <param name="buffer">The character array to write. </param>
        public override void WriteLine(char[] buffer)
        {
            OutputTabs();
            Writer.WriteLine(buffer);
            TabsPending = true;
        }

        /// <summary>Writes the text representation of a Double, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">The double to write. </param>
        public override void WriteLine(double value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes the text representation of an integer, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">The integer to write. </param>
        public override void WriteLine(int value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes the text representation of an 8-byte integer, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">The 8-byte integer to write. </param>
        public override void WriteLine(long value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes the text representation of a Single, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">The single to write. </param>
        public override void WriteLine(float value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes the text representation of a Single, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">The single to write. </param>
        public override void WriteLine(decimal value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes the specified string, followed by a line terminator, to the text stream.</summary>
        /// <param name="s">The string to write. </param>
        public override void WriteLine(string s)
        {
            OutputTabs();
            Writer.WriteLine(s);
            TabsPending = true;
        }

        /// <summary>Writes the text representation of a UInt32, followed by a line terminator, to the text stream.</summary>
        /// <param name="value">A UInt32 to output. </param>
        public override void WriteLine(uint value)
        {
            OutputTabs();
            Writer.WriteLine(value);
            TabsPending = true;
        }

        /// <summary>Writes out a formatted string, followed by a line terminator, using the same semantics as specified.</summary>
        /// <param name="arg0">The object to write into the formatted string. </param>
        /// <param name="format">The formatting string. </param>
        public override void WriteLine(string format, object arg0)
        {
            OutputTabs();
            Writer.WriteLine(format, arg0);
            TabsPending = true;
        }

        /// <summary>Writes out a formatted string, followed by a line terminator, using the same semantics as specified.</summary>
        /// <param name="arg">The argument array to output. </param>
        /// <param name="format">The formatting string to use. </param>
        public override void WriteLine(string format, params object[] arg)
        {
            OutputTabs();
            Writer.WriteLine(format, arg);
            TabsPending = true;
        }

        /// <summary>Writes a subarray of characters, followed by a line terminator, to the text stream.</summary>
        /// <param name="count">The number of characters to write. </param>
        /// <param name="buffer">The character array to write data from. </param>
        /// <param name="index">Starting index in the buffer. </param>
        public override void WriteLine(char[] buffer, int index, int count)
        {
            OutputTabs();
            Writer.WriteLine(buffer, index, count);
            TabsPending = true;
        }

        /// <summary>Writes out a formatted string, followed by a line terminator, using the same semantics as specified.</summary>
        /// <param name="arg0">The first object to write into the formatted string. </param>
        /// <param name="arg1">The second object to write into the formatted string. </param>
        /// <param name="format">The formatting string to use. </param>
        public override void WriteLine(string format, object arg0, object arg1)
        {
            OutputTabs();
            Writer.WriteLine(format, arg0, arg1);
            TabsPending = true;
        }
        #endregion

        #region WriteMultiValue
        public void WriteMultiValue(params string[] values)
        {
            foreach (var t in values)
            {
                Write(t);
            }
        }

        #endregion

        #region WriteMultiValueLine
        public void WriteMultiValueLine(params string[] values)
        {
            for (var i = 0; i < values.Length - 1; i++)
            {
                Write(values[i]);
            }

            if (values.Length > 0)
            {
                WriteLine(values[values.Length - 1]);
            }
        }
        #endregion

        #region WriteLineNoTabs
        /// <summary>
        /// Writes the specified string to a line without tabs.
        /// </summary>
        public void WriteLineNoTabs()
        {
            Writer.WriteLine();
        }

        /// <summary>Writes the specified string to a line without tabs.</summary>
        /// <param name="s">The string to write. </param>
        public void WriteLineNoTabs(string s)
        {
            Writer.WriteLine(s);
        }
        #endregion

        #region WriteBrace Open/Close
        public void WriteBraceOpen()
        {
            WriteLine("{");
            Indent++;
        }

        public void WriteBraceClose()
        {
            Indent--;
            WriteLine("}");
        }
        #endregion

        #region WriteAttribute
        /// <summary>
        /// Writes the attribute.
        /// </summary>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="attributeValue">The attribute value.</param>
        public void WriteAttribute(string attributeName, string attributeValue)
        {
            WriteAttribute(attributeName, attributeValue, true, true);
        }
        /// <summary>
        /// Writes the attribute.
        /// </summary>
        /// <param name="attributeName">Name of the attribute.</param>
        /// <param name="attributeValue">The attribute value.</param>
        /// <param name="isSingleQuotes">if set to <c>true</c> [is single quotes surrounding the value].</param>
        /// <param name="isSpaceBeforeAttribute">if set to <c>true</c> [is space before attribute].</param>
        public void WriteAttribute(string attributeName, string attributeValue, bool isSingleQuotes, bool isSpaceBeforeAttribute)
        {
            if (isSpaceBeforeAttribute)
            {
                Write(" ");
            }
            Write(attributeName);
            Write("=");
            Write(isSingleQuotes ? "'" : "\"");
            Write(attributeValue);
            Write(isSingleQuotes ? "'" : "\"");
        }
        #endregion

        #region WriteElement
        /// <summary>
        /// Writes the element.
        /// </summary>
        /// <param name="elementName">Name of the element.</param>
        /// <param name="elementValue">The element value.</param>
        public void WriteElement(string elementName, string elementValue)
        {
            Write("<");
            Write(elementName);
            Write(">");

            Write(elementValue);

            Write("</");
            Write(elementName);
            WriteLine(">");
        }
        #endregion

        #region Encoding
        /// <summary>Gets the encoding for the text writer to use.</summary>
        public override Encoding Encoding
        {
            get
            {
                return Writer.Encoding;
            }
        }
        #endregion

        #region WriteTimeStamp
        public void WriteTimeStamp()
        {
            Write(DateTime.Now.ToUniversalTime());
        }
        public void WriteTimeStampLine()
        {
            WriteLine(DateTime.Now.ToUniversalTime());
        }
        #endregion

        #region WriteXml
        public void WriteXml(IToXml xml)
        {
            Write(xml.ToXml());
        }
        public void WriteXmlLine(IToXml xml)
        {
            WriteLine(xml.ToXml());
        }
        #endregion

        #region WriteNameValue
        public void WriteNameValue(string name, int value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
        }
        public void WriteNameValue(string name, float value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
        }
        public void WriteNameValue(string name, decimal value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
        }
        public void WriteNameValue(string name, double value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
        }
        public void WriteNameValue(string name, bool value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
        }
        public void WriteNameValue(string name, DateTime? value)
        {
            WriteNameValue(name, value == null ? DateUtil.MinValue.ToString(CultureInfo.InvariantCulture) : value.ToString());
        }
        public void WriteNameValue(string name, Enum value)
        {
            WriteNameValue(name, value.ToString());
        }
        public void WriteNameValue(string name, string value)
        {
            if (NameValuePadding > 0 && name != null)
            {
                Write(name.PadRight(NameValuePadding));
            }
            else
            {
                Write(name);
            }
            Write(NameValueSeperator);
            Write(value);
        }
        public void WriteNameValue(NameValueDo nameValue)
        {
            WriteNameValue(nameValue.Name, nameValue.Value);
        }
        public void WriteNameValueLine(string name, int value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
            WriteLine();
        }
        public void WriteNameValueLine(string name, float value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
            WriteLine();
        }
        public void WriteNameValueLine(string name, decimal value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
            WriteLine();
        }
        public void WriteNameValueLine(string name, double value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
            WriteLine();
        }
        public void WriteNameValueLine(string name, bool value)
        {
            WriteNameValue(name, value.ToString(CultureInfo.InvariantCulture));
            WriteLine();
        }
        public void WriteNameValueLine(string name, DateTime? value)
        {
            WriteNameValue(name, value);
            WriteLine();
        }
        public void WriteNameValueLine(string name, string value)
        {
            WriteNameValue(name, value);
            WriteLine();
        }
        public void WriteNameValueLine(string name, Enum value)
        {
            WriteNameValue(name, value);
            WriteLine();
        }
        public void WriteNameValueLine(NameValueDo nameValue)
        {
            WriteNameValue(nameValue.Name, nameValue.Value);
            WriteLine();
        }
        #endregion

        #region WriteTemplateBraceProperty
        public void WriteTemplateBraceProperty(string template, params string[] keyValues)
        {
            var value = StrUtil.ReplaceBraceProperty(template, keyValues);

            Write(value);
        }
        public void WriteTemplateBraceProperty(string template, IDictionary<string, string> keyValues)
        {
            var value = StrUtil.ReplaceBraceProperty(template, keyValues);

            Write(value);
        }
        public void WriteTemplateBraceProperty(string template, ITokenReplacer tokenReplacer)
        {
            var value = StrUtil.ReplaceBraceProperty(template, tokenReplacer);

            Write(value);
        }
        #endregion

        public ReplacePropertyDelimiterType ReplacePropertyDelimiter { get; set; }

        #region WriteExpandTemplate
        public void WriteExpandTemplate(string template, params string[] keyValues)
        {
            string value;
            switch (ReplacePropertyDelimiter)
            {
                case ReplacePropertyDelimiterType.Square:
                    value = StrUtil.ReplaceSquareProperty(template, keyValues);
                    break;
                case ReplacePropertyDelimiterType.Hash:
                    value = StrUtil.ReplaceHashProperty(template, keyValues);
                    break;
                default:
                    value = StrUtil.ReplaceBraceProperty(template, keyValues);
                    break;
            }

            Write(value);
        }
        public void WriteExpandTemplate(string template, IDictionary<string, string> keyValues)
        {
            string value;
            switch (ReplacePropertyDelimiter)
            {
                case ReplacePropertyDelimiterType.Square:
                    value = StrUtil.ReplaceSquareProperty(template, keyValues);
                    break;
                case ReplacePropertyDelimiterType.Hash:
                    value = StrUtil.ReplaceHashProperty(template, keyValues);
                    break;
                default:
                    value = StrUtil.ReplaceBraceProperty(template, keyValues);
                    break;
            }

            Write(value);
        }
        public void WriteExpandTemplate(string template, ITokenReplacer tokenReplacer)
        {
            string value;
            switch (ReplacePropertyDelimiter)
            {
                case ReplacePropertyDelimiterType.Square:
                    value = StrUtil.ReplaceSquareProperty(template, tokenReplacer);
                    break;
                case ReplacePropertyDelimiterType.Hash:
                    value = StrUtil.ReplaceHashProperty(template, tokenReplacer);
                    break;
                default:
                    value = StrUtil.ReplaceBraceProperty(template, tokenReplacer);
                    break;
            }

            Write(value);
        }
        #endregion

        #region WriteDivider

        public void WriteDivider(bool isTerminator = true)
        {
            if (isTerminator)
            {
                WriteLine(StrUtil.Replicate(LineLength, LineCharacter));
            }
            else
            {
                Write(StrUtil.Replicate(LineLength, LineCharacter));
            }
        }

        public void WriteDivider(string heading, bool isTerminator = true)
        {
            var hd = "[ " + heading + " ]";

            if (isTerminator)
            {
                WriteLine(hd.PadRight(LineLength, LineCharacter));
            }
            else
            {
                Write(hd.PadRight(LineLength, LineCharacter));
            }
        }
        #endregion

        #region InnerWriter
        public TextWriter InnerWriter
        {
            get
            {
                return Writer;
            }
        }
        #endregion

        public TextWriter Writer { get; protected set; }

        public string TabString { get; set; }

        #region Indent (Property)
        private int _indent;

        public int Indent
        {
            get { return _indent; }
            set
            {
                if (value < 0)
                {
                    value = 0;
                }
                _indent = value;
            }
        }
        #endregion

        public bool TabsPending { get; set; }

        #region NewLine
        public override string NewLine
        {
            get { return Writer.NewLine; }
            set { Writer.NewLine = value; }
        }
        #endregion

        #region LineLength
        private int _lineLength = 100;

        public int LineLength
        {
            get { return _lineLength; }
            set { _lineLength = value; }
        }
        #endregion

        #region LineCharacter
        private char _lineCharacter = '-';

        public char LineCharacter
        {
            get { return _lineCharacter; }
            set { _lineCharacter = value; }
        }
        #endregion

        public string NameValueSeperator { get; set; }

        public int NameValuePadding { get; set; }

        #region Data
		private IDictionary<string, object> Data { get; set; }

        public void DataSet<T>(string key, T value)
        {
            if (Data.ContainsKey(key))
            {
                Data[key] = value;
            }
            else
            {
                Data.Add(key, value);
            }
        }
        public T DataGet<T>(string key)
        {
            return Data.ContainsKey(key) 
                ? (T) Data[key] 
                : default(T);
        }
        #endregion    
    }
}
